package com.imm.da.utils

import android.content.Context
import android.os.Parcelable
import android.util.Log
import com.tencent.mmkv.MMKV
import com.tencent.mmkv.MMKVHandler
import com.tencent.mmkv.MMKVLogLevel
import com.tencent.mmkv.MMKVRecoverStrategic
import java.util.concurrent.ConcurrentHashMap

/**
 * <p>------------------------------------------------------
 * <p> Copyright (C) 2021 zhangmz, All rights reserved.
 * <p> If there is technical discussion, you can contact zhangmz90@foxmail.com
 * <p>------------------------------------------------------
 * <p> 继承tencent mmkv框架
 * <p>
 *
 * @author Created by zhangmz
 * @date on 2022/7/6
 */
object DataShareUtil : MMKVHandler {

    private const val TAG = "DataShare"

    /**
     * 默认保存的文件名称
     */
    private const val defaultKey = "mmkv"

    private val kvMap: ConcurrentHashMap<String, MMKV> by lazy {
        ConcurrentHashMap<String, MMKV>()
    }

    /**
     * data/data/包名/files/mmkv
     */
    @JvmStatic
    fun getDir(con: Context?): String{
        return con?.let {
            "${it.filesDir.absolutePath}/mmkv"
        }?: "mmkv"
    }

    /**
     * 默认开启 mmkv 原生log日志
     */
    private var kvLog: Boolean? = true

    @JvmStatic
    fun initMMKV(con: Context?, log: Boolean? = true, rootDir: String? = null){
        con?.let {
            val dir = MMKV.initialize(it, rootDir?: "${it.filesDir.absolutePath}/mmkv")
            Log.v(TAG, "mmkv save file dir:$dir")
            kvLog = log
            MMKV.setLogLevel(if (kvLog != false) MMKVLogLevel.LevelInfo else MMKVLogLevel.LevelNone)
        }?: Log.e(TAG, "context can not be null")
    }

    /**
     * 根据name获得不同的MMKV对象，name不同，保存的文件名也不同
     */
    private fun get(name: String? = defaultKey, mode: Boolean = false): MMKV {
        Log.v(TAG, "get, name:$name")
        var mmkv: MMKV? = null
        name?.let {
            mmkv = if (kvMap.containsKey(it)){
                kvMap[it]
            }else {
                kvMap[it] = MMKV.mmkvWithID(it, if (mode) MMKV.MULTI_PROCESS_MODE else MMKV.SINGLE_PROCESS_MODE, null)
                kvMap[it]
            }
        }?: let {
            kvMap[defaultKey] = MMKV.mmkvWithID(defaultKey, if (mode) MMKV.MULTI_PROCESS_MODE else MMKV.SINGLE_PROCESS_MODE, null)
            mmkv = kvMap[defaultKey]
        }
        return mmkv?: MMKV.mmkvWithID(defaultKey, if (mode) MMKV.MULTI_PROCESS_MODE else MMKV.SINGLE_PROCESS_MODE, null)
    }

    /**
     * 保存数据，name为空则保存在mmkv文件中
     */
    fun save(key: String?, value: Any?, name: String?): Boolean{
        var result = false
        if (key.isNullOrEmpty() || value == null){
            Log.e(TAG, "key or value can not be null")
            return result
        }
        return runCatching {
            get(name).run {
                result = when(value){
                    is Int -> encode(key, value)
                    is Long -> encode(key, value)
                    is Double -> encode(key, value)
                    is Float -> encode(key, value)
                    is Boolean -> encode(key, value)
                    is Parcelable -> encode(key, value)
                    else -> encode(key, value as? String)
                }
            }
            result
        }.onFailure {
            Log.e(TAG, "save value to mmkv fail:$it")
        }.getOrDefault(result)
    }

    /**
     * 取数据，defaultValue不可为空，不然不知道取值的类型了，默认String，有可能取不到
     * name为空则保存在mmkv文件中
     */
    fun load(key: String?, defaultValue: Any? = null, name: String?): Any? {
        var result = defaultValue
        if (key.isNullOrEmpty()){
            Log.e(TAG, "key can not be null")
            return result
        }
        runCatching {
            get(name).run {
                result = when(defaultValue) {
                    is Int -> decodeInt(key, defaultValue)
                    is Long -> decodeLong(key, defaultValue)
                    is Double -> decodeDouble(key, defaultValue)
                    is Float -> decodeFloat(key, defaultValue)
                    is Boolean -> decodeBool(key, defaultValue)
                    else -> decodeString(key)
                }
            }
        }.onFailure {
            Log.e(TAG, "load value to mmkv fail:$it")
        }
        return result
    }

    /**
     * 取实现Parcelable接口的类
     */
    fun <T: Parcelable> load(key: String?, type: Class<T>, name: String?): T?{
        return runCatching {
            get(name).run {
                decodeParcelable(key, type)
            }
        }.onFailure {
            Log.e(TAG, "load parcelable to mmkv fail:$it")
        }.getOrDefault(null)
    }

    /**
     * 循环删除多个key下的value
     */
    fun remove(vararg keys: String?, name: String?){
        if (keys.isEmpty()){
            Log.e(TAG, "key can not be null")
            return
        }
        runCatching {
            get(name).apply {
                removeValuesForKeys(keys)
            }
        }.onFailure {
            Log.e(TAG, "remove value to mmkv fail:$it")
        }
    }

    /**
     * 在 crc 校验失败，MMKV 默认会丢弃所有数据
     * OnErrorRecover 恢复数据，但不保证数据的准确性, OnErrorDiscard 默认丢弃，不处理
     */
    override fun onMMKVCRCCheckFail(mmapID: String?): MMKVRecoverStrategic {
        Log.v(TAG, "onMMKVCRCCheckFail,mmapID:$mmapID")
        return MMKVRecoverStrategic.OnErrorRecover
    }

    /**
     * 文件长度错误，MMKV 默认会丢弃所有数据
     * OnErrorRecover 恢复数据，但不保证数据的准确性, OnErrorDiscard 默认丢弃，不处理
     */
    override fun onMMKVFileLengthError(mmapID: String?): MMKVRecoverStrategic {
        Log.v(TAG, "onMMKVFileLengthError,mmapID:$mmapID")
        return MMKVRecoverStrategic.OnErrorRecover
    }

    override fun wantLogRedirecting(): Boolean {
        return kvLog?: true
    }

    override fun mmkvLog(
        level: MMKVLogLevel?,
        file: String?,
        line: Int,
        function: String?,
        message: String?
    ) {
        val log = "<" + file.toString() + ":" + line.toString() + "::" + function.toString() + "> " + message
        when(level){
            MMKVLogLevel.LevelDebug -> Log.d(TAG, log)
            MMKVLogLevel.LevelInfo -> Log.i(TAG, log)
            MMKVLogLevel.LevelWarning -> Log.w(TAG, log)
            else -> Log.e(TAG, log)
        }
    }

}